﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace SkypeMonitorService
{
	internal class RunAs
	{
		public const UInt32 Infinite = 0xffffffff;
		public const Int32 Startf_UseStdHandles = 0x00000100;
		public const Int32 StdOutputHandle = -11;
		public const Int32 StdErrorHandle = -12;

		[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
		public struct StartupInfo
		{
			public int cb;
			public String reserved;
			public String desktop;
			public String title;
			public int x;
			public int y;
			public int xSize;
			public int ySize;
			public int xCountChars;
			public int yCountChars;
			public int fillAttribute;
			public int flags;
			public UInt16 showWindow;
			public UInt16 reserved2;
			public byte reserved3;
			public IntPtr stdInput;
			public IntPtr stdOutput;
			public IntPtr stdError;
		}

		internal struct ProcessInformation
		{
			public IntPtr process;
			public IntPtr thread;
			public int processId;
			public int threadId;
		}


		[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
		[return: MarshalAs(UnmanagedType.Bool)]
		internal static extern bool CreateProcessWithLogonW(String userName, String domain, String password, UInt32 logonFlags,
			String applicationName, String commandLine, UInt32 creationFlags,
			UInt32 environment, String currentDirectory, ref   StartupInfo startupInfo, out  ProcessInformation processInformation);

		[DllImport("kernel32.dll", SetLastError = true)]
		internal static extern bool GetExitCodeProcess(IntPtr process, ref UInt32 exitCode);

		[DllImport("Kernel32.dll", SetLastError = true)]
		internal static extern UInt32 WaitForSingleObject(IntPtr handle, UInt32 milliseconds);

		[DllImport("Kernel32.dll", SetLastError = true)]
		internal static extern IntPtr GetStdHandle(IntPtr handle);

		[DllImport("Kernel32.dll", SetLastError = true)]
		internal static extern bool CloseHandle(IntPtr handle);

		[DllImport("Wtsapi32.dll", SetLastError = true)]
		internal static extern IntPtr WTSOpenServer(string serverName);
		[DllImport("Wtsapi32.dll", SetLastError = true)]
		internal static extern void WTSCloseServer(IntPtr hServer);
		[DllImport("Wtsapi32.dll", SetLastError = true)]
		[return:MarshalAs( UnmanagedType.Bool)]
		internal static extern bool WTSQueryUserToken(uint SessionId,out IntPtr phToken);
		[DllImport("Kernel32.dll", SetLastError = true)]
		internal static extern uint WTSGetActiveConsoleSessionId();

		private const int UOI_FLAGS = 1;
		private const int UOI_NAME = 2;
		private const int UOI_TYPE = 3;
		private const int UOI_USER_SID = 4;

		[DllImport("user32.dll", SetLastError = true)]
		[return: MarshalAs(UnmanagedType.Bool)]
		static extern bool GetUserObjectInformation(IntPtr hObj, int nIndex, [Out] byte[] pvInfo, uint nLength, out uint lpnLengthNeeded);

		// Note: This returns a handle, but it shouldn't be closed.  The Avalon
		// team says CloseWindowStation would ignore this handle.  So there
		// isn't a lot of value to switching to SafeHandle here.
		[DllImport("USER32.dll", SetLastError = true, ExactSpelling = true)]
		internal static extern IntPtr GetProcessWindowStation();

		[DllImport("KERNEL32.dll", CharSet = CharSet.Auto, SetLastError = true)]
		internal static extern IntPtr GetCurrentProcess();

		#region winApi I/O
		[DllImport("KERNEL32.dll", CharSet = CharSet.Auto, SetLastError = true, BestFitMapping = false)]
		[System.Runtime.Versioning.ResourceExposure(System.Runtime.Versioning.ResourceScope.Machine)]
		internal static extern int GetWindowsDirectory(StringBuilder sb, int length);
		[DllImport("KERNEL32.dll", CharSet = CharSet.Auto, SetLastError = true, BestFitMapping = false)]
		internal static extern int GetSystemDirectory(StringBuilder sb, int length);

		[DllImport("KERNEL32.dll", SetLastError = true)]
		internal static extern bool LockFile(Microsoft.Win32.SafeHandles.SafeFileHandle handle, int offsetLow, int offsetHigh, int countLow, int countHigh);

		[DllImport("KERNEL32.dll", SetLastError = true)]
		internal static extern bool UnlockFile(Microsoft.Win32.SafeHandles.SafeFileHandle handle, int offsetLow, int offsetHigh, int countLow, int countHigh);

		[DllImport("KERNEL32.dll", CharSet = CharSet.Auto, SetLastError = true, BestFitMapping = false)]
		internal static extern bool GetVolumeInformation(String drive, StringBuilder volumeName, int volumeNameBufLen, out int volSerialNumber,
			out int maxFileNameLen, out int fileSystemFlags, StringBuilder fileSystemName, int fileSystemNameBufLen);

		[DllImport("KERNEL32.dll", CharSet = CharSet.Auto, SetLastError = true, BestFitMapping = false)]
		internal static extern bool SetEnvironmentVariable(string lpName, string lpValue);

		[DllImport("KERNEL32.dll", CharSet = CharSet.Auto, SetLastError = true)]
		internal static extern uint GetCurrentProcessId();

		[DllImport("KERNEL32.dll", CharSet = CharSet.Auto, BestFitMapping = false)]
		internal extern static int GetComputerName(StringBuilder nameBuffer, ref int bufferSize);

		#endregion

		
		public void StartProcessAs(string command, string workDir, string user, string password)
		{
			StartupInfo startupInfo = new StartupInfo();
			startupInfo.reserved = null;
			startupInfo.flags &= Startf_UseStdHandles;
			startupInfo.stdOutput = (IntPtr)StdOutputHandle;
			startupInfo.stdError = (IntPtr)StdErrorHandle;

			UInt32 exitCode = 123456;
			ProcessInformation processInfo = new ProcessInformation();

			String domain = System.Environment.UserDomainName;

			if (!CreateProcessWithLogonW(user, domain, password,
					(UInt32)1, command, command, (UInt32)0, (UInt32)0,
					workDir, ref startupInfo, out processInfo))
				throw new System.ComponentModel.Win32Exception(System.Runtime.InteropServices.Marshal.GetLastWin32Error());

			uint result = WaitForSingleObject(processInfo.process, Infinite);

			GetExitCodeProcess(processInfo.process, ref exitCode);

			CloseHandle(processInfo.process);
			CloseHandle(processInfo.thread);
		}
	}
}
